/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	Minidump.h
	Crash reporting.

****************************************************************************/

#ifdef _MSC_VER

#include "dbghelp.h"
#include <time.h>

#define MAXIDUMP 0 // 1 = Full dump, 0 = partial dump (recommended).

BeginMaxsiNamespace

class MaxsiEngineMiniDumper;

LINK type_ExceptionFilter ME_ExceptionFilter;

bool ME_IsDataSectionNeeded( const WCHAR* pModuleName ) ;

BOOL CALLBACK ME_MiniDumpCallback(
	PVOID                            pParam, 
	const PMINIDUMP_CALLBACK_INPUT   pInput, 
	PMINIDUMP_CALLBACK_OUTPUT        pOutput 
);

class dbghelp_dll : public DLL
{
public:	
	Declare_Class(dbghelp_dll, DLL);

	DECLARE_FUNCTION_WINAPI(BOOL,MiniDumpWriteDump,(
	HANDLE			hProcess,
	DWORD			dwPid,
	HANDLE			hFile,
	MINIDUMP_TYPE	DumpType,

	CONST PMINIDUMP_EXCEPTION_INFORMATION		ExceptionParam,
	CONST PMINIDUMP_USER_STREAM_INFORMATION		UserStreamParam,
	CONST PMINIDUMP_CALLBACK_INFORMATION		CallbackParam));

	virtual bool	Import	( char* LibraryPath )
	{
		if ( BaseClass::Import(LibraryPath) )
		{
			IMPORT_FUNCTION(MiniDumpWriteDump);
			return true;
		}
		return false;
	}
};

class MaxsiEngineMiniDumper
{
public:
	MaxsiEngineMiniDumper(char* AppName);
	~MaxsiEngineMiniDumper();
};

static MESTR* ApplicationName;

LONG WINAPI ExceptionFilter(__in struct _EXCEPTION_POINTERS *ExceptionInfo )
{
	LONG Result		=	EXCEPTION_CONTINUE_SEARCH;

	// Get the date
	__int64		LocalTime;
	tm			CurrentTime;
	char		CurrentTime_str[26];
	errno_t		err;

	_time64( &LocalTime );

	err = _gmtime64_s( &CurrentTime, &LocalTime );
	if (err)
	{
		MESTR* Error = BuildString(1,_MESTR("An unhandled error occured in ") _MESTR(Application) _MESTR(". Maxsi Engine failed to generate a minidump file because _gmtime64_s failed. ") _MESTR(SupportId(31)));
		MessageBoxM(NULL,Error,_MESTR("Maxsi Engine"),MB_ICONERROR);
		delete[] Error;
		return Result;				
	}

	err = asctime_s(CurrentTime_str, 26, &CurrentTime);
	if (err)
	{
		char* Error = BuildString(1,_MESTR("An unhandled error occured in ") _MESTR(Application) _MESTR(". Maxsi Engine failed to generate a minidump file because asctime_s failed. ") _MESTR(SupportId(32)));
		MessageBoxM(NULL,Error,_MESTR("Maxsi Engine"),MB_ICONERROR);
		delete[] Error;
		return Result;		
	}

	// Now that we got the date, proceed and generate the filename

	char*		Dir		=	GetWorkingDirectory();
	char*		File	=	BuildString(5,Dir,Application,"_v" __DATE__ "_date",CurrentTime_str,".mdmp");
	str_replace(File+2,":","_"); // plus two to avoid c: becoming c_
	str_replace(File,"\n","_"); // asctime_s adds a line break? wut

	char*		DllFile	=	BuildString(2,Dir,"dbghelp.dll");

	// Load the dbghelp.dll
	
	dbghelp_dll* dll = new dbghelp_dll;

	if ( dll )
	{
		if ( !dll->Import(DllFile) )
		{
			dll->Import("dbghelp.dll"); // Perhaps the system has a proper copy
		}
		
		if (dll->Executable!=NULL)
		{
			// create the file
			HANDLE hFile = CreateFileA( File, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
										FILE_ATTRIBUTE_NORMAL, NULL );

			if (hFile!=INVALID_HANDLE_VALUE)
			{
				_MINIDUMP_EXCEPTION_INFORMATION ExInfo;

				ExInfo.ThreadId = ::GetCurrentThreadId();
				ExInfo.ExceptionPointers = ExceptionInfo;
				ExInfo.ClientPointers = NULL;

					
				MINIDUMP_CALLBACK_INFORMATION mci; 
#if MAXIDUMP
				mci.CallbackRoutine     = NULL;
#else
				mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)ME_MiniDumpCallback;
#endif					
				mci.CallbackParam       = 0; 					

				MINIDUMP_TYPE mdt       = (MINIDUMP_TYPE)(
#if MAXIDUMP
						MiniDumpWithFullMemory |
#else
						MiniDumpWithPrivateReadWriteMemory | 
						MiniDumpWithDataSegs | 
#endif
						MiniDumpWithHandleData |
						MiniDumpWithFullMemoryInfo | 
						MiniDumpWithThreadInfo | 
						MiniDumpWithUnloadedModules ); 

				// write the dump
				BOOL bOK = dll->MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, &ExInfo, 0, &mci ); 

				if (bOK)
				{
					char* Error = BuildString(3,"An unhandled error occured in "Application". A minidump file containing information about how this crash happened has been saved to\n\n",File,"\n\nPlease send this file to the developers of this product. " SupportId(33));
					MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
					delete[] Error;
					Result = EXCEPTION_EXECUTE_HANDLER;
				}
				else
				{				

					char* Error = BuildString(1,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because something went wrong while writing the minidump. " SupportId(34));
					MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
					delete[] Error;
				}
				CloseHandle(hFile);
			}
			else
			{
				char* Error = BuildString(3,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because the target file path couldn't be opened\n\n",File, "\n\n" SupportId(35));
				MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
				delete[] Error;
			}
		}
		else
		{
			char* Error = BuildString(1,"An unhandled error occured in "Application". Maxsi Engine failed to generate a minidump file because dbghelp.dll wasn't found. " SupportId(36));
			MessageBoxA(NULL,Error,"Maxsi Engine",MB_ICONERROR);
			delete[] Error;
		}
	}

	// Clean up
	delete[]	File;
	delete[]	Dir;
	delete[]	DllFile;
	delete		dll;
	return Result;
}

BOOL CALLBACK ME_MiniDumpCallback(
	PVOID                            pParam, 
	const PMINIDUMP_CALLBACK_INPUT   pInput, 
	PMINIDUMP_CALLBACK_OUTPUT        pOutput 
) 
{
	BOOL bRet = FALSE; 


	// Check parameters 

	if( pInput == 0 ) 
		return FALSE; 

	if( pOutput == 0 ) 
		return FALSE; 


	// Process the callbacks 

	switch( pInput->CallbackType ) 
	{
		case IncludeModuleCallback: 
		{
			// Include the module into the dump 
			bRet = TRUE; 
		}
		break; 

		case IncludeThreadCallback: 
		{
			// Include the thread into the dump 
			bRet = TRUE; 
		}
		break; 

		case ModuleCallback: 
		{
			// Are data sections available for this module ? 

			if( pOutput->ModuleWriteFlags & ModuleWriteDataSeg ) 
			{
				// Yes, they are, but do we need them? 

				if( !ME_IsDataSectionNeeded( pInput->Module.FullPath ) ) 
				{
					wprintf( L"Excluding module data sections: %s \n", pInput->Module.FullPath ); 

					pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg); 
				}
			}

			bRet = TRUE; 
		}
		break; 

		case ThreadCallback: 
		{
			// Include all thread information into the minidump 
			bRet = TRUE;  
		}
		break; 

		case ThreadExCallback: 
		{
			// Include this information 
			bRet = TRUE;  
		}
		break; 

		case MemoryCallback: 
		{
			// We do not include any information here -> return FALSE 
			bRet = FALSE; 
		}
		break; 

		case CancelCallback: 
			break; 
	}

	return bRet; 

}


bool ME_IsDataSectionNeeded( const WCHAR* pModuleName ) 
{
	// Check parameters 

	if( pModuleName == 0 ) 
	{
		ME_UNDEFINED_BEHAVIOR("Parameter pModuleName is null in ME_IsDataSectionNeeded()!" ); 
		return false; 
	}

	// Extract the module name 

	WCHAR szFileName[_MAX_FNAME] = L""; 

	_wsplitpath_s( pModuleName, NULL, 0, NULL, 0, szFileName,_MAX_FNAME, NULL, 0 );

	// Compare the name with the list of known names and decide 

	// Remove all known system libraries
	if( _wcsicmp( szFileName, L"ntdll" ) == 0 ) 
	{
		return true; // Appears we should include this one
	}
	else if( _wcsicmp( szFileName, L"user32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"gdi32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"Ws2_32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"zdll" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"zlib" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"comdlg32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"ComCtl32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"Mswsock" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"Ole32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"shell32" ) == 0 ) 
	{
		return false;
	}
	else if( _wcsicmp( szFileName, L"Advapi32" ) == 0 ) 
	{
		return false;
	}

	// Complete 

	return true; // Assume we need any other modules.

}


MaxsiEngineMiniDumper::MaxsiEngineMiniDumper(char* AppName)
{
	SetUnhandledExceptionFilter(Maxsi::ExceptionFilter);
	ApplicationName		=	BuildString(1,AppName);
	ME_ExceptionFilter	=	ExceptionFilter;
}

MaxsiEngineMiniDumper::~MaxsiEngineMiniDumper()
{
	if ( ApplicationName )
	{
		delete[] ApplicationName;
	}
}	

Maxsi::MaxsiEngineMiniDumper MiniDumper(Application);

EndMaxsiNamespace

#endif
